#import "HTTPNPKFileResponse.h"
#import "HTTPConnection.h"
#import "HTTPLogging.h"
#import <unistd.h>
#import <fcntl.h>

// Log levels : off, error, warn, info, verbose
// Other flags: trace
static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE;

#define NULL_FD -1

NPK_TEAKEY npkKey[4] = { 19, 80, 8, 30 };

@implementation HTTPNPKFileResponse

- (id)initWithFilePath:(NSString *)fpath elementName:(NSString*)eName forConnection:(HTTPConnection *)parent
{
	if((self = [super init]))
	{
		HTTPLogTrace();
		
		connection = parent; // Parents retain children, children do NOT retain parents
		
		filePath = [fpath copy];
		elementName = [eName copy];
		
		if (filePath == nil)
		{
			HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE);
			
			[self release];
			return nil;
		}
		
		NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
		if (fileAttributes == nil)
		{
			HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath);
			
			[self release];
			return nil;
		}
		
		// get length
		NPK_PACKAGE _pack = npk_package_open( [fpath UTF8String], npkKey );
		NPK_ENTITY _entity = npk_package_get_entity(_pack, [elementName UTF8String] );
		NPK_SIZE size = npk_entity_get_size( _entity );
		npk_package_close(pack);
			
		fileLength = size;
		fileOffset = 0;
		
		aborted = NO;
		
		// We don't bother opening the file here.
		// If this is a HEAD request we only need to know the fileLength.
	}
	return self;
}

- (void)abort
{
	HTTPLogTrace();
	
	[connection responseDidAbort:self];
	aborted = YES;
}

- (BOOL)openFile
{
	HTTPLogTrace();

	pack = npk_package_open( [filePath UTF8String], npkKey );
	entity = npk_package_get_entity( pack, [elementName UTF8String] );
	
	if ( pack == NULL )
	{
		HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath);
		
		[self abort];
		return NO;
	}

	if ( entity == NULL )
	{
		HTTPLogError(@"%@[%p]: entity not exist. entity: %@", THIS_FILE, self, elementName );
		
		[self abort];
		return NO;
	}

	HTTPLogVerbose(@"%@[%p]: Open npk. package(%@), entity(%@)", THIS_FILE, self, filePath, elementName );
	
	return YES;
}

- (BOOL)openFileIfNeeded
{
	if (aborted)
	{
		// The file operation has been aborted.
		// This could be because we failed to open the file,
		// or the reading process failed.
		return NO;
	}
	
	if ( pack != NULL && entity != NULL )
	{
		return YES;
	}
	
	return [self openFile];
}

- (UInt64)contentLength
{
	HTTPLogTrace();
	
	return fileLength;
}

- (UInt64)offset
{
	HTTPLogTrace();
	
	return fileOffset;
}

- (void)setOffset:(UInt64)offset
{
	HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
	
	if (![self openFileIfNeeded])
	{
		// File opening failed,
		// or response has been aborted due to another error.
		return;
	}
	
	fileOffset = offset;
}

- (NSData *)readDataOfLength:(NSUInteger)length
{
	HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length);
	
	if (![self openFileIfNeeded])
	{
		// File opening failed,
		// or response has been aborted due to another error.
		return nil;
	}
	
	// Determine how much data we should read.
	// 
	// It is OK if we ask to read more bytes than exist in the file.
	// It is NOT OK to over-allocate the buffer.
	UInt64 bytesLeftInFile = fileLength - fileOffset;
	
	NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile);
	
	// for 8 byte align in front of buffer & back of buffer
	NSUInteger npkBufferSize = bytesToRead + (8*2); 
	
	// Make sure buffer is big enough for read request.
	// Do not over-allocate.
	if (buffer == NULL || bufferSize < npkBufferSize)
	{
		bufferSize = npkBufferSize;
		buffer = reallocf(buffer, (size_t)bufferSize);
		
		if (buffer == NULL)
		{
			HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self);
			
			[self abort];
			return nil;
		}
	}
	
	// Perform the read
	HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, bytesLeftInFile);
	
	NSUInteger npkReadOffset = (fileOffset / 8) * 8;
	NSUInteger npkBytesToRead = (bytesToRead / 8) * 8 + 8;
	NSUInteger endPoint = npkReadOffset + npkBytesToRead;
	
	if( endPoint > fileLength )
		npkBytesToRead = fileLength - npkReadOffset;
	
	BOOL readOK = npk_entity_read_partial(entity, buffer, npkReadOffset, npkBytesToRead);
	
	// Check the results
	if ( !readOK )
	{
		HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath);
		
		[self abort];
		return nil;
	}
	else
	{
		HTTPLogVerbose(@"%@[%p]: Read %d bytes from file", THIS_FILE, self, bytesToRead );
		void* realDataStartPoint = buffer;
		realDataStartPoint += ( fileOffset - npkReadOffset );

		fileOffset += bytesToRead;
		return [NSData dataWithBytes:realDataStartPoint length:bytesToRead];
	}
}

- (BOOL)isDone
{
	BOOL result = (fileOffset == fileLength);
	
	HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
	
	return result;
}

- (NSString *)filePath
{
	return filePath;
}

- (void)dealloc
{
	HTTPLogTrace();
	
	if (pack != NULL)
	{
		HTTPLogVerbose(@"%@[%p]: Close pack (%@) ", THIS_FILE, self, filePath );
		npk_package_close(pack);
		pack = NULL;
		entity = NULL;
	}
	
	if (buffer)
		free(buffer);
	
	[filePath release];
	[super dealloc];
}

@end
